package edu.co.gap.recognition;

import static com.googlecode.javacv.cpp.opencv_core.CV_32FC1;
import static com.googlecode.javacv.cpp.opencv_core.CV_TERMCRIT_ITER;
import static com.googlecode.javacv.cpp.opencv_core.IPL_DEPTH_32F;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateImage;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateMat;
import static com.googlecode.javacv.cpp.opencv_core.cvTermCriteria;
import static com.googlecode.javacv.cpp.opencv_highgui.cvSaveImage;
import static com.googlecode.javacv.cpp.opencv_legacy.CV_EIGOBJ_NO_CALLBACK;
import static com.googlecode.javacv.cpp.opencv_legacy.cvCalcEigenObjects;
import static com.googlecode.javacv.cpp.opencv_legacy.cvEigenDecomposite;

import java.io.FileNotFoundException;
import java.nio.FloatBuffer;
import java.util.List;

import com.googlecode.javacpp.Pointer;
import com.googlecode.javacpp.PointerPointer;
import com.googlecode.javacv.cpp.opencv_core.CvMat;
import com.googlecode.javacv.cpp.opencv_core.CvSize;
import com.googlecode.javacv.cpp.opencv_core.CvTermCriteria;
import com.googlecode.javacv.cpp.opencv_core.IplImage;

public class EigenFace {

	// atributes
	private IplImage[] facesToIdentify = null;
	IplImage pAvgTrainImg = null; // the average image
	CvMat[] projectedTrainFaces = null; // collection of projected training
										// faces
	int nTrainFaces;
	int nEigens;
	CvMat trainPersonNumMat = null; // projected training faces
	// mio
	Pointer[] pointersEigenVectArr;
	Pointer[] pointersFaceImgArr;

	// states
	private boolean train = false;// if EigenFace execute training method

	private void addIdentifyFace() {
		// TODO Auto-generated method stub
	}

	public EigenFace(IplImage[] images) throws Exception {
		training(images);
	}

	public EigenFace(List<IplImage> images) throws Exception {
		this(images.toArray(new IplImage[0]));
	}

	private void doPCA(IplImage[] images) {

		CvTermCriteria calcLimit;
		CvSize faceImgSize = new CvSize();

		// //set the number of eigenvalues to use
		// int nEigens=nTrainFaces-1;

		// allocate the eigenvector images
		faceImgSize = faceImgSize.width(images[0].width());
		faceImgSize = faceImgSize.height(images[0].height());

		IplImage[] eigenVect = null; // igenvectors

		eigenVect = new IplImage[nEigens];

		for (int i = 0; i < eigenVect.length; i++) {
			eigenVect[i] = cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);
		}

		CvMat eigenValMat = null; // eigenvalues

		// allocate the eigenvalues array
		eigenValMat = cvCreateMat(1, nEigens, CV_32FC1);

		pAvgTrainImg = cvCreateImage(faceImgSize, IPL_DEPTH_32F, 1);

		// set the PCA termination criterion
		calcLimit = cvTermCriteria(CV_TERMCRIT_ITER, nEigens, 1);

		pointersFaceImgArr = getPointersFrom(images);
		pointersEigenVectArr = getPointersFrom(eigenVect);

		if (true) {
			System.out.println(eigenValMat.toString(2));
			FloatBuffer fbs = eigenValMat.getFloatBuffer();
			System.out.println(fbs);
			for (int i = 0; i < eigenVect.length; i++) {
				cvSaveImage("eigenVectArr" + i + ".jpg", eigenVect[i]);
			}
			cvSaveImage("avgimage.jpg", pAvgTrainImg);
		}

		cvCalcEigenObjects(nTrainFaces, new PointerPointer(pointersFaceImgArr),
				new PointerPointer(pointersEigenVectArr),
				CV_EIGOBJ_NO_CALLBACK, 0, null, calcLimit, pAvgTrainImg,
				eigenValMat.getFloatBuffer());

		if (true) {
			System.out.println(eigenValMat.toString(2));
			FloatBuffer fb = eigenValMat.getFloatBuffer();
			System.out.println(fb);
		}

	}

	private Pointer[] getPointersFrom(IplImage[] some) {
		Pointer[] pointers = new Pointer[some.length];

		for (int i = 0; i < pointers.length; i++) {
			pointers[i] = new Pointer(some[i]);
		}
		return pointers;
	}

	public void training(IplImage[] images) throws Exception {
		if (train)
			return;

		nTrainFaces = images.length;

		nEigens = nTrainFaces - 1;

		if (images.length < 2) {
			throw new Exception("Need 2 or more images");
		}
		
		projectedTrainFaces = new CvMat[images.length];

		doPCA(images);

		// nrows //ncols //datatype
		CvMat projectedTrainFaceMatActual = cvCreateMat(nTrainFaces, nEigens,
				CV_32FC1);

		// borrame
		float[] projectedTestFace = new float[nEigens];

		for (int i = 0; i < nTrainFaces; i++) {

			projectedTrainFaceMatActual = cvCreateMat(nTrainFaces, nEigens,
					CV_32FC1);
			// IplImage int pointer int pointer IplImage FloatBuffer
			cvEigenDecomposite(images[i], nEigens, new PointerPointer(
					pointersEigenVectArr), 0, null, pAvgTrainImg,
					projectedTrainFaceMatActual.getFloatBuffer());
			projectedTrainFaces[i] = projectedTrainFaceMatActual;

			if (true) {
				System.out.println("projectedTrainFaceMAt="
						+ projectedTrainFaceMatActual);
				for (int j = 0; j < nEigens; j++) {
					System.out.println("projectedTrainFaceMAt[0," + j + "]="
							+ projectedTrainFaceMatActual.get(0, j));
				}

				cvEigenDecomposite(images[i], nEigens, new PointerPointer(
						pointersEigenVectArr), 0, null, pAvgTrainImg,
						projectedTestFace);
				printArray(projectedTestFace);
			}

		}

		// training method correctly execute
		train = true;
	}

	public int recognize(IplImage inputImage) throws FileNotFoundException {
		int i, nTestFaces = 1;
		float[] projectedTestFace = null;

		projectedTestFace = new float[nEigens];

		int iNearest, nearest, truth;
		cvEigenDecomposite(inputImage, nEigens, new PointerPointer(
				pointersEigenVectArr), 0, null, pAvgTrainImg, projectedTestFace);

		if (true) {
			printArray(projectedTestFace);
		}

		iNearest = findNearestNeighbor(projectedTestFace);
		// truth= (int) trainPersonNumMat.get(0);
		// nearest=(int) trainPersonNumMat.get(iNearest);
		// System.out.printf("iNearest=%d,nearest= %d, Truth= %d\n",iNearest,nearest,truth);
		// System.out.printf("nearest= %d, Truth= %d\n",nearest,truth);
		System.out.println("nearest=" + iNearest);

		return iNearest;
	}

	private int findNearestNeighbor(float[] projectedTestFace) {
		// TODO Auto-generated method stub

		double leastDistSq = Integer.MAX_VALUE;

		int iTrain, INearest = 0;

		for (iTrain = 0; iTrain < nTrainFaces; iTrain++) {

			double distSq = 0;
			for (int i = 0; i < nEigens; i++) {

				double projectedInfo = projectedTrainFaces[iTrain].get(i);

				if (true) {
					System.out.println("projectedTrainFaceMAt[0," + i + "]="
							+ projectedInfo);
				}

				float d_i = (float) (projectedTestFace[i] - projectedInfo);

				distSq += d_i * d_i;

			}

			if (distSq < leastDistSq) {
				leastDistSq = distSq;

				INearest = iTrain;

			}

		}

		return INearest;
	}

	public void recognize(IplImage[] inputImage) {
		// TODO Auto-generated method stub

	}

	private void printArray(float[] a) {

		for (int i = 0; i < a.length; i++) {
			System.out.printf("a[%d]=%f\n", i, a[i]);
		}

	}

	private void printArray(double[] a) {

		for (int i = 0; i < a.length; i++) {
			System.out.printf("a[%d]=%f\n", i, a[i]);
		}

	}

	public static void main(String[] args) {

	}

}
